跳到主要内容

Java 操作 ElasticSearch 查询

准备练习的数据

索引名称:sms-logs-index 索引类型:sms-logs-type

创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class SmsLogs {
@JsonIgnore
private String id; // id

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date createDate; // 创建时间

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date sendDate; // 发送时间

private String longCode; // 发送的长号码
private String mobile; // 手机号
private String corpName; // 发送公司名称
private String smsContent; // 短信内容
private Integer start; // 短信发送状态,0成功,1失败
private Integer operatorId; // 运营商编号 1移动 2联通 3电信
private String province; // 省份
private String ipAddr; // 服务器ip地址
private Integer replyTotal; // 短信状态报告返回时长(秒)
private Integer fee; // 费用
}

创建索引

这里可以直接在 ES 上创建

private RestHighLevelClient client = ESClient.getClient();
private String index = "sms-logs-index";

// 创建索引
@Test
public void CreateIndexForSms() throws IOException {
// 创建索引
Settings.Builder settings = Settings.builder()
.put("number_of_shards", 5)
.put("number_of_replicas", 1);

// 指定mappings
XContentBuilder mappings = XContentFactory.jsonBuilder()
.startObject()
.startObject("properties")
.startObject("createDate")
.field("type", "date")
.field("format", "8yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
.endObject()
.startObject("sendDate")
.field("type", "date")
.field("format", "8yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
.endObject()
.startObject("longCode")
.field("type", "keyword")
.endObject()
.startObject("mobile")
.field("type", "keyword")
.endObject()
.startObject("corpName")
.field("type", "keyword")
.endObject()
.startObject("smsContent")
.field("type", "text")
.field("analyzer", "ik_max_word")
.endObject()
.startObject("state")
.field("type", "integer")
.endObject()
.startObject("operatorId")
.field("type", "integer")
.endObject()
.startObject("province")
.field("type", "keyword")
.endObject()
.startObject("ipAddr")
.field("type", "ip")
.endObject()
.startObject("replyTotal")
.field("type", "integer")
.endObject()
.startObject("fee")
.field("type", "long")
.endObject()
.endObject()
.endObject();

// 将 settings 和 mappings 封装为Request对象
CreateIndexRequest request = new CreateIndexRequest(index)
.settings(settings)
.mapping(mappings);

// 通过Client连接
CreateIndexResponse res = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(res.toString());
}

创建测试数据

private String type = "_doc";

// 测试数据
@Test
public void CreateTestData() throws IOException {
// 准备多个json数据
SmsLogs s1 = new SmsLogs("1", new Date(), new Date(), "10690000988", "1370000001", "途虎养车", "【途虎养车】亲爱的刘女士,您在途虎购买的货物单号(Th12345678)", 0, 1, "上海", "10.126.2.9", 10, 3);
SmsLogs s2 = new SmsLogs("2", new Date(), new Date(), "84690110988", "1570880001", "韵达快递", "【韵达快递】您的订单已配送不要走开哦,很快就会到了,配送员:王五,电话:15300000001", 0, 1, "上海", "10.126.2.8", 13, 5);
SmsLogs s3 = new SmsLogs("3", new Date(), new Date(), "10698880988", "1593570001", "滴滴打车", "【滴滴打车】指定的车辆现在距离您1000米,马上就要到了,请耐心等待哦,司机:李师傅,电话:13890024793", 0, 1, "河南", "10.126.2.7", 12, 10);
SmsLogs s4 = new SmsLogs("4", new Date(), new Date(), "20697000911", "1586890005", "中国移动", "【中国移动】尊敬的客户,您充值的话费100元,现已经成功到账,您的当前余额为125元,2020年12月18日14:35", 0, 1, "北京", "10.126.2.6", 11, 4);
SmsLogs s5 = new SmsLogs("5", new Date(), new Date(), "18838880279", "1562384869", "网易", "【网易】亲爱的玩家,您已经排队成功,请尽快登录到网易云游戏进行游玩,祝您游戏愉快---网易云游戏", 0, 1, "杭州", "10.126.2.5", 10, 2);
// 转为json
ObjectMapper mapper = new ObjectMapper();
String json1 = mapper.writeValueAsString(s1);
String json2 = mapper.writeValueAsString(s2);
String json3 = mapper.writeValueAsString(s3);
String json4 = mapper.writeValueAsString(s4);
String json5 = mapper.writeValueAsString(s5);

// request,将数据封装进去
BulkRequest request = new BulkRequest();
request.add(new IndexRequest(index, type, s1.getId()).source(json1, XContentType.JSON));
request.add(new IndexRequest(index, type, s2.getId()).source(json2, XContentType.JSON));
request.add(new IndexRequest(index, type, s3.getId()).source(json3, XContentType.JSON));
request.add(new IndexRequest(index, type, s4.getId()).source(json4, XContentType.JSON));
request.add(new IndexRequest(index, type, s5.getId()).source(json5, XContentType.JSON));
// client执行
BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
System.out.println(response);
}

然后在 Kibana 中检查数据

term、terms 查询

from 分页

下面这里的 from 关键字是用于分页的

POST /sms-logs-index/_doc/_search
{
"from": 0, // 类似limit,指定查询第一页
"size": 5, // 指定一页查询几条
"query": {
"term": {
"province": {
"value": "上海" // 这里省略 } 号了,下同

注意:Elastic Search 为了避免深分页,不允许使用分页(from + size)查询 10000 条以后的数据,因此如果要查询第 10000 条以后的数据,要使用 Elastic Search 提供的 scroll 游标 来查询,具体看下面

term 查询(单条件)

term 查询是完全匹配的,搜索之前不会对搜索的关键字进行分词,比如要搜河南省

POST /sms-logs-index/_doc/_search
{
"from": 0, // 类似limit,指定查询第一页
"size": 5, // 指定一页查询几条
"query": {
"term": {
"province": {
"value": "上海" // 这里省略 } 号了,下同

可以看到查询结果,我们只要 _source 中的内容即可

在 Java 中查询

// term查询
@Test
public void termQuery() throws IOException {
//1 request
SearchRequest request = new SearchRequest(index);
request.types(type);

//2 指定查询条件
// 指定form ,size
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.from(0);
builder.size(5);
// 指定查询条件,province字段,内容为北京
builder.query(QueryBuilders.termQuery("province","上海"));
request.source(builder);
//3执行查询
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
//4 获取到数据
for (SearchHit hit : response.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}

terms 查询(多条件)

terms 查询,也是不会对条件进行分词,但是这个可以指定多条件,比如查询地点为上海的或者河南的

POST /sms-logs-index/_doc/_search
{
"query": {
"terms": {
"province": [
"上海",
"河南"

Java 形式的查询

// terms查
@Test
public void termsQuery() throws IOException {
// request
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.termsQuery("province", "上海", "河南"));
request.source(builder);
// 执行查询
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取数据
for (SearchHit hit : response.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}

match 查询(自动匹配)

match 查询会根据不同的情况切换不同的策略

  • 如果查询 date 类型和数字,就会将查询的结果自动的转换为数字或者日期
  • 如果是不能被分词的内容(keyword),就不会进行分词查询
  • 如果是 text 这种,就会根据分词的方式进行查询

match 的底层就是多个 term 查询,加了条件判断等

match_all 查询

它会将全部的 doc 查询出来

POST /sms-logs-index/_doc/_search
{
"query": {
"match_all": {}
}
}

Java 的形式

// match_all查询
@Test
public void matchAllQuery() throws IOException {
// request
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
request.source(builder);
// 执行查询
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取数据
for (SearchHit hit : response.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}

match 查询

match 查询会针对不同的类型执行不同的策略

查询 text 类型的数据会对条件进行分词,例如下面查询这个 "smsContent" 消息内容

POST /sms-logs-index/_doc/_search
{
"query": {
"match": {
"smsContent": "电话"

Java 的形式

// match查询
@Test
public void matchQuery() throws IOException {
// request
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("smsContent","电话号码"));
request.source(builder);
// 执行查询
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// 获取数据
for (SearchHit hit : response.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}

布尔 match 查询

可以查询既包含条件 1,又包含条件 2 的内容,也就是 and 的效果,也可以实现 or 的效果

POST /sms-logs-index/_doc/_search
{
"query": {
"match": {
"smsContent": {
"query": "电话 快递",
"operator": "and" // or

Java 的形式

// 布尔match查询
@Test
public void booleanMatchQuery() throws IOException {
// request
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询条件
SearchSourceBuilder builder = new SearchSourceBuilder(); // 指定and或者or
builder.query(QueryBuilders.matchQuery("smsContent","电话 快递").operator(Operator.AND));
request.source(builder);
// 执行查询
SearchResponse response = client.search(request,RequestOptions.DEFAULT);
// 获取数据
for (SearchHit hit : response.getHits().getHits()) {
Map<String, Object> result = hit.getSourceAsMap();
System.out.println(result);
}
}

multi_match 查询

针对多个 key 对应一个 value 进行查询,比如下面就是查询地区带中国,或者内容带中国

POST /sms-logs-index/_doc/_search
{
"query": {
"multi_match": {
"query": "中国",
"fields": ["province","smsContent"]

Java 查询简单写,其余的部分均一样

builder.query(QueryBuilders.multiMatchQuery("中国","smsContent","province"));

其他查询

注意:prefix,fuzzy,wildcard,regexp,查询的效率相对比较低,要求效率高的时候,不要使用这个

id 查询

GET /sms-logs-index/_doc/1

Java 的形式

// id查询
@Test
public void idMatchQuery() throws IOException {
// 使用getRequest
GetRequest request = new GetRequest(index,type,"1");
// 执行查询
GetResponse response = client.get(request, RequestOptions.DEFAULT);
// 输出结果
Map<String, Object> result = response.getSourceAsMap();
System.out.println(result);
}

ids 查询

给以多个 id,查询多个结果,类似 MySQL 的 where id in(1,2,3.....)

// ids查询
POST /sms-logs-index/_doc/_search
{
"query": {
"ids": {
"values": ["1","2","3"]

Java 的形式

// ids查询
@Test
public void idsQuery() throws IOException {
// 这个属于复杂查询需要使用searchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.idsQuery().addIds("1","2","3"));

request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 输出结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

prefix 查询(前缀查询)

听名字就是前缀查询,查询子首的,可以指定指定字段的前缀,从而查询到指定的文档,可以实现类似百度输入后弹出提示的效果

POST /sms-logs-index/_doc/_search
{
"query": {
"prefix": {
"corpName": {
"value": "滴滴" // 这样就能搜到所有关于滴滴开头的公司名称了

Java 的形式

// prefix查询
@Test
public void prefixQuery() throws IOException {
// 依然使用SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.prefixQuery("corpName","滴滴"));
request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

fuzzy 查询(模糊查询)

模糊查询,根据输入的内容大概的搜索,可以输入错别字,不是很稳定,比如输入 网一 来搜索网易就搜不到

// fuzzy查询
POST /sms-logs-index/_doc/_search
{
"query": {
"fuzzy": {
"corpName": {
"value": "中国移不动",
"prefix_length": 2 // 可选项,可以指定前几个字符是不能错的

这里搜索中国移不动,依然可以搜索到中国移动

Java 的形式

// fuzzy查询
@Test
public void fuzzyQuery() throws IOException {
// 依然使用SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.fuzzyQuery("corpName","中国移不动"));
//builder.query(QueryBuilders.fuzzyQuery("corpName","中国移不动").prefixLength(2));
request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

wildcard 查询(通配符查询)

通配查询,和 MySQL 的 like 关键字一样,可以在搜索的时候设置占位符,通配符等实现模糊匹配

POST /sms-logs-index/_doc/_search
{
"query": {
"wildcard": {
"corpName": {
"value": "中国*" // *代表通配符,?代表占位符 ,比如:中国? 就是搜中国开头的三个字的内容

Java 的形式

// wildcard查询
@Test
public void wildcardQuery() throws IOException {
// 依然使用SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.wildcardQuery("corpName","中国??"));
request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

range 查询(范围查询)

范围查询,只针对数值类型

这里的范围是 大于等于 的,这里能查询到 fee 等于 5,或者 10 的,如果想要 < 或者 > 的效果可以使用 gtlt

// range查询
POST /sms-logs-index/_doc/_search
{
"query": {
"range": {
"fee": {
"gte": 5, // gt
"lte": 10 // lt

这里的范围,指定字符串的 5 或者 int 类型的 5 都是可以的,es 会自动的进行转换

// range查询
@Test
public void rangeQuery() throws IOException {
// 依然使用SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.rangeQuery("fee").gte("5").lte("10"));
request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

regexp 查询(正则)

正则查询,通过编写的正则表达式匹配内容

注意:prefix,fuzzy,wildcard,regexp,查询的效率相对比较低,要求效率高的时候,不要使用这个

// regexp查询
POST /sms-logs-index/_doc/_search
{
"query": {
"regexp": {
"mobile": "15[0-9]{8}" // 这里查询电话号码15开头的,后面的数字8位任意

Java 的形式

// regexp查询
@Test
public void regexpQuery() throws IOException {
// 依然使用SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 查询
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.regexpQuery("mobile","15[0-9]{8}"));
request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

深分页 scroll

针对 term 查询的 from 和 size 的大小有限制,from + size 的总和不能大于1万,所以 Elastic Search 为了避免深分页,不允许使用分页(from + size)查询 10000 条以后的数据,因此如果要查询第 10000 条以后的数据,要使用 Elastic Search 提供的 scroll 游标 来查询

下面来看下两种分页查询的区别:

from + size 查询的步骤:先进行分词,然后把词汇去分词库检索,得到文档的 id,然后去分片中把数据拿出来,然后根据 score 进行排序,然后根据 from 和 size 舍弃一部分,最后将结果返回

scroll + size 查询的步骤:同样分词,通过分词库找到文档的 id,将文档的 id 存放入 ES 的上下文中(内存中),第四步根据指定的 size 去 ES 中拿指定数量的数据,拿完数据的 docId 会从上下文移除,如果需要下一页数据,会去 ES 的上下文中找

但是 scroll 也有缺点,不适合实时查询,因为是从内存中找以前查询的,拿到的数据不是最新的,这个查询比较适合做后台管理

POST /sms-logs-index/_doc/_search?scroll=1m  // 这里指定在内存中保存的时间,1m就是1分钟
{
"query": {
"match_all": {}
},
"size": 2,
"sort": [ // 这里指定排序规则
{
"fee": {
"order": "desc"
}
}
]
}

可以看到 _scroll_id

查询下一页的数据

POST /_search/scroll
{
"scroll_id":"这里写id", // 这里写上第一次查询的_scroll_id
"scroll":"1m" // 重新指定存在时间,否则直接从内存删除了
}

通过上一次查询到的 "scroll_id" 进行下一次查询

如果看完第二页不想看下去了,想直接删除掉内存中的数据:

DELETE /_search/scroll/scroll的id

Java 的形式

// scroll查询
@Test
public void scrollQuery() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 指定scroll的信息,存在内存1分钟
request.scroll(TimeValue.timeValueMinutes(1L));
// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.size(1);
builder.sort("fee", SortOrder.ASC);
builder.query(QueryBuilders.matchAllQuery());
request.source(builder);
// 执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);
// 获取第一页的结果结果,以及scrollId
String scrollId = response.getScrollId();
System.out.println("------第一页------");
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}

// 循环遍历其余页
while (true){
// SearchScrollRequest,指定生存时间,scrollId
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(TimeValue.timeValueMinutes(1L));
// 执行查询
SearchResponse scrollResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);

// 如果查询到了数据
SearchHit[] hits = scrollResponse.getHits().getHits();
if (hits !=null && hits.length>0){
System.out.println("------下一页------");
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
}else {
System.out.println("-----最后一页-----");
break;
}
}
// ClearScrollRequest
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
// 删除ScoreId
ClearScrollResponse scrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);

System.out.println("删除scroll成功了吗?"+scrollResponse.isSucceeded());
}

输出如下:

delete-by-query 查询并删除

根据 term,match 等查询方式删除大量的文档

注意:如果是大量的删除,不推荐这个方式,太耗时了,因为是根据查询的 id 一个一个删除,而查询本身也很消耗性能,推荐新建一个 index,把保留的部分保留到新的 index

POST /sms-logs-index/_doc/_delete_by_query   // 把查询出来的结果删除
{
"query":{
"range":{
"fee":{
"lt":4

Java 的形式

// deleteByQuery查询
@Test
public void deleteByQuery() throws IOException {
// DeleteByQueryRequest
DeleteByQueryRequest request = new DeleteByQueryRequest(index);
request.types(type);

// 指定检索条件
request.setQuery(QueryBuilders.rangeQuery("fee").lt(4));

// 执行删除
BulkByScrollResponse response = client.deleteByQuery(request, RequestOptions.DEFAULT);

System.out.println(response);
}

复合查询

bool 查询

将多个查询条件以一定的逻辑组合在一起

  • must:表示and的意思,所有的条件都符合才能找到
  • must_not:把满足条件的都去掉的结果
  • should:表示 or 的意思

例如满足以下条件的 bool 查询:

  • 查询省份是上海或者河南
  • 运营商不是联通
  • smsContent 中包含中国和移动
POST /sms-logs-index/_doc/_search
{
"query": {
"bool":{
"should": [ // or
{
"term": {
"province": {
"value": "上海"
}
}
},
{
"term": {
"province": {
"value": "河南"
}
}
}
],
"must_not": [ // 不包括
{
"term": {
"operatorId": {
"value": "2" // 注:运营商编号 1移动 2联通 3电信
}
}
}
],
"must": [ // and
{
"match": {
"smsContent": "中国"
}
},
{
"match": {
"smsContent": "移动"
}
}
]
}
}
}

Java 的形式

// boolQuery查询
@Test
public void boolQuery() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 上海或者河南
boolQuery.should(QueryBuilders.termQuery("province","武汉"));
boolQuery.should(QueryBuilders.termQuery("province","河南"));
// 运营商不是联通
boolQuery.mustNot(QueryBuilders.termQuery("operatorId",2));
// 包含中国和移动
boolQuery.must(QueryBuilders.matchQuery("smsContent","中国"));
boolQuery.must(QueryBuilders.matchQuery("smsContent","移动"));
// 指定使用bool查询
builder.query(boolQuery);
request.source(builder);

// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

boolsting 分数查询

分数查询,查询的结果都是有匹配度一个分数,可以针对内容,让其分数大,或者小,达到排前,排后的效果

  • positive: 只有匹配到 positive 的内容,才会放到结果集,也就是放查询条件的地方
  • negative:如果匹配到的 positive 和 negative,就会降低文档的分数 source
  • negative_boost:指定降低分数的系数,必须小于 1.0,比如:10分 这个系数为 0.5 就会变为 5分

关于分数的计算:

  • 关键字在文档出现的频次越高,分数越高
  • 文档的内容越短,分数越高
  • 搜索时候,指定的关键字会被分词,分词内容匹配分词库,匹配的个数越多,分数就越高
// boosting查询
POST /sms-logs-index/_doc/_search
{
"query": {
"boosting": {
"positive": {
"match": {
"smsContent": "亲爱的"
}
},
"negative": {
"match": {
"smsContent": "网易"
}
},
"negative_boost": 0.5
}
}
}

比如这里,因为加上了 negative,网易原来的分数是 1 左右,现在是 0.43

Java 的形式

// boostingQuery查询
@Test
public void boostingQuery() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoostingQueryBuilder boostingQueryBuilder = QueryBuilders.boostingQuery(
QueryBuilders.matchQuery("smsContent", "亲爱的"),
QueryBuilders.matchQuery("smsContent", "网易")
).negativeBoost(0.5f);

builder.query(boostingQueryBuilder);
request.source(builder);
// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

filter 查询(过滤器查询)

过滤器查询:根据条件去查询文档,不会计算分数(即,只匹配满足过滤器的),而且 filter 会对经常查询的内容进行缓存

前面的 query 查询:根据条件进行查询,计算分数,根据分数进行排序,不会进行缓存

// filter查询
POST /sms-logs-index/_doc/_search
{
"query": {
"bool":{
"filter": [ // 过滤器可以指定多个
{
"term":{
"corpName": "中国移动"
}
},
{
"range":{
"fee": {
"lte": 5

Java 的形式

// filterQuery查询
@Test
public void filterQuery() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);
// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.filter(QueryBuilders.termQuery("corpName","中国移动"));
boolQuery.filter(QueryBuilders.rangeQuery("fee").lte(5));

builder.query(boolQuery);
request.source(builder);
// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

高亮查询

将用户输入的内容,以高亮的样式展示出来,查询的结果会附带在 hits 下面以单独的形式返回,不会影响查询的结果

ES 提供了一个 hightlight 的属性,和 query 同级别,其属性如下:

  • fragment_size:指定要展示多少内容,可以看到百度的内容后面有...还有很长,默认100个
  • pre_tags:指定前缀标签 比如:就是红色
  • post_tags:指定后缀标签:
  • fields:指定哪几个 field 以高亮形式返回

如下搜索引擎返回的高亮效果

// hight查询
POST /sms-logs-index/_doc/_search
{
"query": {
"match": { // 查询
"smsContent": "亲爱的"
}
},
"highlight": { // 高亮显示
"fields": {
"smsContent": {} // 要高亮展示的内容
},
"pre_tags": "",
"post_tags": "",
"fragment_size": 10
}
}

如下所示:

Java 的形式

// highlightQuery查询
@Test
public void highlightQuery() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);

// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchQuery("smsContent", "亲爱的"));
// 高亮显示
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("smsContent",10) // 只显示10个字
.preTags("<font color='read'>").postTags(""); // 红色展示

builder.highlighter(highlightBuilder);
request.source(builder);
// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果,拿高亮的内容
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getHighlightFields());
}
}

聚合查询

也就是类似 MySQL 的 count,max,avg 等查询,但要更为强大

聚合查询有新的语法

POST /index/type/_search
{
"aggs":{ // 这个名字表示开启聚合查询
"名字":{
"agg_type":{
"属性":"值"
}
}
}
}

去重计数查询

关系型数据库中,比如 MySQL,可以通过 distinct 进行去重,一般分为两种: 1、统计去重后的数量

select distinct(count(1)) from test;

2、获取去重后的结果

select distinct name,sex from person;

Elasticsearch 类似功能的实现方式

// 去重记数查询
POST /sms-logs-index/_doc/_search
{
"aggs": { // 这个名字表示开启聚合查询
"agg": { // 聚合的名字,可以随便自定义(用于取得数据时用)
"cardinality": { // 去重查询
"field": "province" // 需要去重的字段

查询结果如下

命名的是 agg,这里查询的键也是 agg(查询这个结果时就可以用自定义的键了,具体看下面 Java 代码)

Java 的形式

// 去重记数查询
@Test
public void cardinalityQuery() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);

// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.aggregation(AggregationBuilders.cardinality("agg").field("province"));
request.source(builder);
// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果拿到总数,因为 Aggregation 是一个接口,我们需要向下转型,使用实现类的方法才能拿的 value
// 这里就需要使用自定义的键来取得数据了
Cardinality agg = response.getAggregations().get("agg");
long value = agg.getValue();
System.out.println("省份总数为:"+value);

// 拿到查询的内容
for (SearchHit hit : response.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
}

打印的结果如下,去除重复的省份,总共有四个,然后总共有 5条数据

范围统计

根据某个属性的范围,统计文档的个数,针对不同的类型指定不同的方法

  • range:数值
  • date_range:时间
  • ip_range:ip

数值范围查询:

// 范围统计查询(左闭右开),小于号是不带等号的
POST /sms-logs-index/_doc/_search
{
"aggs": {
"agg": {
"range": {
"field": "fee",
"ranges": [
{
"to": 5 // 小于5
},
{
"from": 6, // 大于等于6,小于10
"to": 10
},
{
"from":10 // 大于等于10
}
]
}
}
}
}

时间范围查询

// 时间范围统计查询
POST /sms-logs-index/_doc/_search
{
"aggs": {
"agg": {
"date_range": {
"field": "createDate",
"format": "yyyy", // 指定查询条件,这里是以年为条件
"ranges": [
{
"to": "2000" // 小于2000年
},
{
"from": "2000" // 大于等于2000年
}
]
}
}
}
}

ip 范围查询

// ip范围统计查询
POST /sms-logs-index/_doc/_search
{
"aggs": {
"agg": {
"ip_range": {
"field": "ipAddr",
"ranges": [
{
"from": "10.126.2.7", // 查询这个范围的ip
"to": "10.126.2.10"
}
]
}
}
}
}

Java 的形式

// 范围统计查询
@Test
public void range() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);

// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.aggregation(AggregationBuilders.range("agg").field("fee")
.addUnboundedTo(5) // 指定范围
.addRange(5,10)
.addUnboundedFrom(10));

request.source(builder);
// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果
Range agg = response.getAggregations().get("agg");
for (Range.Bucket bucket : agg.getBuckets()) {
String key = bucket.getKeyAsString();
Object from = bucket.getFrom();
Object to = bucket.getTo();
long docCount = bucket.getDocCount();
System.out.println(String.format("key:%s,from:%s,to:%s,docCount:%s",key,from,to,docCount));
}
}

统计聚合查询(例如求最值)

可以查询属性 field 的最大值,最小值,平均值,平方和.......

POST /sms-logs-index/_doc/_search
{
"aggs": {
"agg": {
"extended_stats": {
"field": "fee"
}
}
}
}

Java 的形式

// 聚合查询
@Test
public void extendedStats() throws IOException {
// SearchRequest
SearchRequest request = new SearchRequest(index);
request.types(type);

// 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.aggregation(AggregationBuilders.extendedStats("agg").field("fee"));

request.source(builder);
// client执行
SearchResponse response = client.search(request, RequestOptions.DEFAULT);

// 获取结果
ExtendedStats agg = response.getAggregations().get("agg");
double max = agg.getMax();
double min = agg.getMin();

System.out.println("fee的最大值为"+max);
System.out.println("fee的最小值为"+min);
}